package com.jiaz.algorithm;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jiaz.dto.PostionScoreDto;
import com.jiaz.dto.UserSimilaryDTO;
import com.jiaz.entity.Postion;
import com.jiaz.mapper.CheckInMapper;
import com.jiaz.mapper.PostionMapper;
import com.jiaz.mapper.UserMapper;
import com.jiaz.pojo.UserCheckInFreq;
import com.jiaz.untils.ResultWrap;
import com.jiaz.untils.constant.VarConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

/**
 * @author jiajiazi
 * @version 1.0
 * @date 2022/5/5 23:03
 */
@Service
@Slf4j
public class UserAlgorithm implements RecommendAlgorithm, RecommendUserAlgorithm {

    @Autowired
    private CheckInMapper checkInMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PostionMapper postionMapper;

    public UserCheckInFreq[] userPostionCheckIn = null;
    public double[] checkInSimilaryCache = null;

    public UserCheckInFreq[] userPostionFreq = null;
    public double[] freqSimilaryCache = null;

    // 相邻节点下标
    public List<Integer>[] closeNodes = null;
    // 相邻节点权重和
    public double[] closeWeightSum = null;

    public double[] pgSimilaryCache = null;


    /**
     * CF_user 用户相似度（签到）
     */
    @Override
    public double[] originAlgorithm() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户签到与否相似度计算");
        if (userPostionCheckIn == null || checkInSimilaryCache == null) {
            userPostionCheckIn = checkInMapper.getUser_Postion_CheckIn().toArray(new UserCheckInFreq[0]);
        }
        int len = userPostionCheckIn.length;
        checkInSimilaryCache = new double[len * (len + 1) / 2];
        int cacheId = 0;
        for (int i = 0; i < len; i++) {
            int[] checkInposti = userPostionCheckIn[i].getCheckInpost();
            for (int j = i; j < len; j++, cacheId++) {
                if (i == j) {
                    checkInSimilaryCache[cacheId] = 1.0;
                    continue;
                }
                int[] checkInpostj = userPostionCheckIn[j].getCheckInpost();

                // 分子
                int moleSum = 0;
                int ptri = 0, ptrj = 0;
                while (ptri < checkInposti.length && ptrj < checkInpostj.length) {
                    int r = checkInposti[ptri] - checkInpostj[ptrj];
                    if (r == 0) {
                        moleSum += 1;
                        ptrj++;
                        ptri++;
                    } else if (r > 0) {
                        ptrj++;
                    } else {
                        ptri++;
                    }
                }
                if (moleSum == 0) {
                    continue;
                }
                // 分母
                double demoSum = Math.sqrt(checkInposti.length) * Math.sqrt(checkInpostj.length);
                if (demoSum == 0) {
                    continue;
                }
                checkInSimilaryCache[cacheId] = moleSum / demoSum;
            }
        }
        stopWatch.stop();
        log.info("【结束】 用户签到与否相似度计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        return checkInSimilaryCache;
    }

    /**
     * @param userId
     * @param venueId 排除较远范围
     * @param topN
     * @return
     */
    @Override
    public PostionScoreDto[] originAlgorithmScore(int userId, int venueId, int topN) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户签到与否-评分计算");
        if (userPostionCheckIn == null || checkInSimilaryCache == null) {
            originAlgorithm();
        }
        int n = userPostionCheckIn.length;
        int userIndex = 0;
        for (int i = 0; i < n; i++) {
            if (userPostionCheckIn[i].getUserId() == userId) {
                userIndex = i;
                break;
            }
        }
        Postion postion = postionMapper.selectById(venueId);
        List<Postion> postionList = postionMapper.selectList(new QueryWrapper<Postion>().select("id","lat","lon"));
        PriorityQueue<PostionScoreDto> postionScoreDtos = new PriorityQueue<>(new Comparator<PostionScoreDto>() {
            @Override
            public int compare(PostionScoreDto o1, PostionScoreDto o2) {
                double d = o1.getScore() - o2.getScore();
                return d == 0 ? 0 : d > 0 ? 1 : -1;
            }
        });

        double demo = 0;
        for (int i = 0; i <= userIndex; i++) {
            demo += checkInSimilaryCache[userIndex + i * n - i / 2 - i * i / 2];
        }
        for (int i = userIndex; i < n; i++) {
            demo += checkInSimilaryCache[i + userIndex * n - userIndex / 2 - userIndex * userIndex / 2];
        }
        for (Postion postionl : postionList) {
            // 距离筛选
            if(ResultWrap.distant(postion.getLat(),postion.getLon(),postionl.getLat(),postionl.getLon())>VarConstant.BEST_DISTANT_USER){
                continue;
            }
            double mole = 0;
            for (int i = 0; i <= userIndex; i++) {
                if (userPostionCheckIn[i].include(postionl.getId())) {
                    mole += checkInSimilaryCache[userIndex + i * n - i / 2 - i * i / 2];
                }
            }
            for (int i = userIndex; i < n; i++) {
                if (userPostionCheckIn[i].include(postionl.getId())) {
                    mole += checkInSimilaryCache[i + userIndex * n - userIndex / 2 - userIndex * userIndex / 2];
                }
            }
            double score = mole / demo;
            if (postionScoreDtos.size() < topN) {
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            } else if (postionScoreDtos.peek().getScore() < score) {
                postionScoreDtos.poll();
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            }
        }
        stopWatch.stop();
        log.info("【结束】 用户签到与否-评分计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        return postionScoreDtos.toArray(new PostionScoreDto[0]);
    }

    /**
     * top 10 好友（签到）
     *
     * @param userId id
     * @return
     */
    public List<UserSimilaryDTO> getUserFriendByTradition(int userId) {
        if (checkInSimilaryCache == null || userPostionCheckIn == null) {
            originAlgorithm();
        }
        int userIndex = 0;
        for (int i = 0; i < userPostionCheckIn.length; i++) {
            if (userPostionCheckIn[i].getUserId() == userId) {
                userIndex = i;
                break;
            }
        }
        int len = userPostionCheckIn.length;
        List<UserSimilaryDTO> userSimilaryDTOS = new ArrayList<UserSimilaryDTO>();
        for (int i = 0; i < userId; i++) {
            double d = checkInSimilaryCache[userId + i * len - i / 2 - i * i / 2];
            if (d > 0) {
                userSimilaryDTOS.add(new UserSimilaryDTO(userPostionCheckIn[i].getUserId(), d));
            }
        }
        for (int i = userId + 1; i < len; i++) {
            double d = checkInSimilaryCache[i + userId * len - userId / 2 - userId * userId / 2];
            if (d > 0) {
                userSimilaryDTOS.add(new UserSimilaryDTO(userPostionCheckIn[i].getUserId(), d));
            }
        }

        userSimilaryDTOS.sort(new Comparator<UserSimilaryDTO>() {
            @Override
            public int compare(UserSimilaryDTO o1, UserSimilaryDTO o2) {
                double dis = o2.getScore() - o1.getScore();
                if (dis < 0) {
                    return -1;
                } else if (dis > 0) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });
        if (userSimilaryDTOS.size() > VarConstant.USER_CHECKIN_TOTAL) {
            return userSimilaryDTOS.subList(0, VarConstant.USER_CHECKIN_TOTAL);
        }
        return userSimilaryDTOS;
    }

    /**
     * CF_user 用户相似度 (频率)
     */
    @Override
    public double[] freqImproveAlgorithm() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户签到频率-相似度计算");
        if (userPostionFreq == null) {
            userPostionFreq = checkInMapper.getUser_Postion_Freq().toArray(new UserCheckInFreq[0]);
        }
        int len = userPostionFreq.length;
        freqSimilaryCache = new double[len * (len + 1) / 2];
        int cacheIndex = 0;
        for (int i = 0; i < len; i++) {
            int[] checkInposti = userPostionFreq[i].getCheckInpost();
            int[] checkInFreqi = userPostionFreq[i].getCheckInFreq();
            for (int j = i; j < len; j++, cacheIndex++) {
                if (i == j) {
                    freqSimilaryCache[cacheIndex] = 1.0;
                    continue;
                }
                int[] checkInpostj = userPostionFreq[j].getCheckInpost();
                int[] checkInFreqj = userPostionFreq[j].getCheckInFreq();
                if (checkInposti == null || checkInpostj == null) {
                    continue;
                }
                // 分子
                int moleSum = 0;
                int ptri = 0, ptrj = 0;
                while (ptri < checkInposti.length && ptrj < checkInpostj.length) {
                    int r = checkInposti[ptri] - checkInpostj[ptrj];
                    if (r == 0) {
                        moleSum += checkInFreqi[ptri++] * checkInFreqj[ptrj++];
                    } else if (r > 0) {
                        ptrj++;
                    } else {
                        ptri++;
                    }
                }
                if (moleSum == 0) {
                    continue;
                }
                // 分母
                double demoSum = Math.sqrt(userPostionFreq[i].getSquareSum()) * Math.sqrt(userPostionFreq[j].getSquareSum());
                if (demoSum == 0) {
                    continue;
                }
                freqSimilaryCache[cacheIndex] = (float) moleSum / demoSum;
            }
        }
        stopWatch.stop();
        log.info("【结束】 用户签到频率-相似度计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        return freqSimilaryCache;
    }

    // int vuneuId 最终topk与之距离筛选
    @Override
    public PostionScoreDto[] freqImproveAlgorithmScore(int userId, int venueId, int topN) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户签到频率-得分计算");
        if (userPostionFreq == null || freqSimilaryCache == null) {
            freqImproveAlgorithm();
        }
        int n = userPostionFreq.length;
        int userIndex = 0;
        for (int i = 0; i < n; i++) {
            if (userPostionFreq[i].getUserId() == userId) {
                userIndex = i;
                break;
            }
        }
        Postion postion = postionMapper.selectById(venueId);
        List<Postion> postionList = postionMapper.selectList(new QueryWrapper<Postion>().select("id","lat","lon"));
        PriorityQueue<PostionScoreDto> postionScoreDtos = new PriorityQueue<>(new Comparator<PostionScoreDto>() {
            @Override
            public int compare(PostionScoreDto o1, PostionScoreDto o2) {
                double d = o1.getScore() - o2.getScore();
                return d == 0 ? 0 : d > 0 ? 1 : -1;
            }
        });

        double demo = 0;
        for (int i = 0; i <= userIndex; i++) {
            demo += freqSimilaryCache[userIndex + i * n - i / 2 - i * i / 2];
        }
        for (int i = userIndex; i < n; i++) {
            demo += freqSimilaryCache[i + userIndex * n - userIndex / 2 - userIndex * userIndex / 2];
        }
        for (Postion postionl : postionList) {
            // 距离筛选
            if(ResultWrap.distant(postion.getLat(),postion.getLon(),postionl.getLat(),postionl.getLon())>VarConstant.BEST_DISTANT_USER){
                continue;
            }
            double mole = 0;
            for (int i = 0; i <= userIndex; i++) {
                if (userPostionFreq[i].include(postionl.getId())) {
                    mole += freqSimilaryCache[userIndex + i * n - i / 2 - i * i / 2];
                }
            }
            for (int i = userIndex; i < n; i++) {
                if (userPostionFreq[i].include(postionl.getId())) {
                    mole += freqSimilaryCache[i + userIndex * n - userIndex / 2 - userIndex * userIndex / 2];
                }
            }
            double score = mole / demo;
            if (postionScoreDtos.size() < topN) {
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            } else if (postionScoreDtos.peek().getScore() < score) {
                postionScoreDtos.poll();
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            }
        }
        stopWatch.stop();
        log.info("【结束】 用户签到频率-得分计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        return postionScoreDtos.toArray(new PostionScoreDto[0]);
    }

    /**
     * 频率相似度 储存DB
     */
    public void CF_User_Freq_DB() {
        if (freqSimilaryCache == null || userPostionFreq == null) {
            freqImproveAlgorithm();
        }
        int n = userPostionFreq.length;
        DecimalFormat decimalFormat = new DecimalFormat("#.#######");
        for (int i = 0; i < userPostionFreq.length; i++) {
            StringBuilder str = new StringBuilder();

            for (int j = 0; j < i; j++) {
                str.append(userPostionFreq[j].getUserId())
                        .append(":")
                        .append(decimalFormat.format(freqSimilaryCache[i + j * n - j / 2 - j * j / 2]))
                        .append(";");
            }
            for (int j = 1 + i; j < n; j++) {
                str.append(userPostionFreq[j].getUserId())
                        .append(":")
                        .append(decimalFormat.format(freqSimilaryCache[j + i * n - i / 2 - i * i / 2]))
                        .append(";");

            }
            userPostionFreq[i].setUserToUser(String.valueOf(str));
        }

        userMapper.updateUserSimilaryByBatch(userPostionFreq);
    }

    public void beforePG_gorithm() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户WPR相似度计算-节点周边信息缓存");
        int n = userPostionFreq.length;
        // 相邻节点下标
        closeNodes = new ArrayList[n];
        // 相邻节点权重和
        closeWeightSum = new double[n];

        for (int i = 0; i < n; i++) {
            double dsum = 0;
            List<Integer> nodes = new ArrayList<>();
            for (int j = 0; j < i; j++) {
                double d = freqSimilaryCache[i + j * n - j / 2 - j * j / 2];
                dsum += d;
                if (d > 0) {
                    nodes.add(j);
                }
            }
            for (int j = i + 1; j < n; j++) {
                double d = freqSimilaryCache[j + i * n - i / 2 - i * i / 2];
                dsum += d;
                if (d > 0) {
                    nodes.add(j);
                }
            }
            closeNodes[i] = nodes;
            closeWeightSum[i] = dsum;
        }
        stopWatch.stop();
        log.info("【结束】 用户WPR相似度计算-节点周边信息缓存，耗时：{}ms",stopWatch.getTotalTimeMillis());
    }

    @Override
    public double[] PG_gorithm(int userIndex) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户WPR相似度计算");
        if (userPostionFreq == null || freqSimilaryCache == null) {
            freqImproveAlgorithm();
        }
        if (closeNodes == null || closeWeightSum == null) {
            beforePG_gorithm();
        }
        int n = userPostionFreq.length;
        double[] pgSimilaryOld = new double[n];
        double dt = 0.0000000001;
        double a = 0.85;
        pgSimilaryOld[userIndex] = 1.0;
        int time = 0;
        while (time < 100000) {
            double tap = 0;
            double[] pgSimilary = new double[n];
            for (int i = 0; i < n; i++) {
                List<Integer> closeNode = closeNodes[i];
                double d = 0;
                double self = i == userIndex ? 1.0 : 0.0;
                for (int cd : closeNode) {
                    d += pgSimilaryOld[cd] * (i < cd ? freqSimilaryCache[cd + i * n - i / 2 - i * i / 2] : freqSimilaryCache[i + cd * n - cd / 2 - cd * cd / 2]) / closeWeightSum[cd];
                }
                d = (1 - a) * self / n + a * d;
                double abs = Math.abs(d - pgSimilaryOld[i]);
                if (abs > tap) {
                    tap = abs;
                }
                pgSimilary[i] = d;
            }
            pgSimilaryOld = pgSimilary;
            time++;
            if (tap <= dt) {
                break;
            }
            if (time % 1000 == 0) {
                log.info("用户WPR相似度计算, 迭代进行第{}次", time);
            }
        }
        stopWatch.stop();
        log.info("【结束】用户WPR相似度计算, 迭代进行{}次，耗时：{}ms", time, stopWatch.getTotalTimeMillis());
        return pgSimilaryOld;
    }

    @Override
    public PostionScoreDto[] PG_gorithmScore(int userId, int venueId, int topN) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 用户WPR得分计算");

        if (userPostionFreq == null || freqSimilaryCache == null) {
            this.freqImproveAlgorithm();
        }
        int n = userPostionFreq.length;
        int userIndex = 0;
        for (int i = 0; i < n; i++) {
            if (userPostionFreq[i].getUserId() == userId) {
                userIndex = i;
                break;
            }
        }
        double[] pgSimilary = PG_gorithm(userIndex);
        double demo = 0;
        for (int i = 0; i < n; i++) {
            demo += pgSimilary[i];
        }
        Postion postion = postionMapper.selectById(venueId);
        List<Postion> postionList = postionMapper.selectList(new QueryWrapper<Postion>().select("id","lat","lon"));
        PriorityQueue<PostionScoreDto> postionScoreDtos = new PriorityQueue<>(new Comparator<PostionScoreDto>() {
            @Override
            public int compare(PostionScoreDto o1, PostionScoreDto o2) {
                double d = o1.getScore() - o2.getScore();
                return d == 0 ? 0 : d > 0 ? 1 : -1;
            }
        });
        for (Postion postionl : postionList) {
            // 距离筛选
            if(ResultWrap.distant(postion.getLat(),postion.getLon(),postionl.getLat(),postionl.getLon())>VarConstant.BEST_DISTANT_USER){
                continue;
            }
            double mole = 0;
            for (int i = 0; i < n; i++) {
                if (userPostionFreq[i].include(postionl.getId())) {
                    mole += pgSimilary[i];
                }
            }
            double score = mole / demo;
            if (postionScoreDtos.size() < topN) {
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            } else if (postionScoreDtos.peek().getScore() < score) {
                postionScoreDtos.poll();
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            }
        }
        stopWatch.stop();
        log.info("【结束】 用户WPR得分计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        return postionScoreDtos.toArray(new PostionScoreDto[0]);
    }

}
